/**
 * Copyright 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule ReactDefaultPerfAnalysis
 */

'use strict'

// Don't try to save users less than 1.2ms (a number I made up)

var _assign = require('object-assign')

var DONT_CARE_THRESHOLD = 1.2
var DOM_OPERATION_TYPES = {
  _mountImageIntoNode: 'set innerHTML',
  INSERT_MARKUP: 'set innerHTML',
  MOVE_EXISTING: 'move',
  REMOVE_NODE: 'remove',
  SET_MARKUP: 'set innerHTML',
  TEXT_CONTENT: 'set textContent',
  setValueForProperty: 'update attribute',
  setValueForAttribute: 'update attribute',
  deleteValueForProperty: 'remove attribute',
  setValueForStyles: 'update styles',
  replaceNodeWithMarkup: 'replace',
  replaceDelimitedText: 'replace'
}

function getTotalTime(measurements) {
  // TODO: return number of DOM ops? could be misleading.
  // TODO: measure dropped frames after reconcile?
  // TODO: log total time of each reconcile and the top-level component
  // class that triggered it.
  var totalTime = 0
  for (var i = 0; i < measurements.length; i++) {
    var measurement = measurements[i]
    totalTime += measurement.totalTime
  }
  return totalTime
}

function getDOMSummary(measurements) {
  var items = []
  measurements.forEach(function (measurement) {
    Object.keys(measurement.writes).forEach(function (id) {
      measurement.writes[id].forEach(function (write) {
        items.push({
          id: id,
          type: DOM_OPERATION_TYPES[write.type] || write.type,
          args: write.args
        })
      })
    })
  })
  return items
}

function getExclusiveSummary(measurements) {
  var candidates = {}
  var displayName

  for (var i = 0; i < measurements.length; i++) {
    var measurement = measurements[i]
    var allIDs = _assign({}, measurement.exclusive, measurement.inclusive)

    for (var id in allIDs) {
      displayName = measurement.displayNames[id].current

      candidates[displayName] = candidates[displayName] || {
        componentName: displayName,
        inclusive: 0,
        exclusive: 0,
        render: 0,
        count: 0
      }
      if (measurement.render[id]) {
        candidates[displayName].render += measurement.render[id]
      }
      if (measurement.exclusive[id]) {
        candidates[displayName].exclusive += measurement.exclusive[id]
      }
      if (measurement.inclusive[id]) {
        candidates[displayName].inclusive += measurement.inclusive[id]
      }
      if (measurement.counts[id]) {
        candidates[displayName].count += measurement.counts[id]
      }
    }
  }

  // Now make a sorted array with the results.
  var arr = []
  for (displayName in candidates) {
    if (candidates[displayName].exclusive >= DONT_CARE_THRESHOLD) {
      arr.push(candidates[displayName])
    }
  }

  arr.sort(function (a, b) {
    return b.exclusive - a.exclusive
  })

  return arr
}

function getInclusiveSummary(measurements, onlyClean) {
  var candidates = {}
  var inclusiveKey

  for (var i = 0; i < measurements.length; i++) {
    var measurement = measurements[i]
    var allIDs = _assign({}, measurement.exclusive, measurement.inclusive)
    var cleanComponents

    if (onlyClean) {
      cleanComponents = getUnchangedComponents(measurement)
    }

    for (var id in allIDs) {
      if (onlyClean && !cleanComponents[id]) {
        continue
      }

      var displayName = measurement.displayNames[id]

      // Inclusive time is not useful for many components without knowing where
      // they are instantiated. So we aggregate inclusive time with both the
      // owner and current displayName as the key.
      inclusiveKey = displayName.owner + ' > ' + displayName.current

      candidates[inclusiveKey] = candidates[inclusiveKey] || {
        componentName: inclusiveKey,
        time: 0,
        count: 0
      }

      if (measurement.inclusive[id]) {
        candidates[inclusiveKey].time += measurement.inclusive[id]
      }
      if (measurement.counts[id]) {
        candidates[inclusiveKey].count += measurement.counts[id]
      }
    }
  }

  // Now make a sorted array with the results.
  var arr = []
  for (inclusiveKey in candidates) {
    if (candidates[inclusiveKey].time >= DONT_CARE_THRESHOLD) {
      arr.push(candidates[inclusiveKey])
    }
  }

  arr.sort(function (a, b) {
    return b.time - a.time
  })

  return arr
}

function getUnchangedComponents(measurement) {
  // For a given reconcile, look at which components did not actually
  // render anything to the DOM and return a mapping of their ID to
  // the amount of time it took to render the entire subtree.
  var cleanComponents = {}
  var writes = measurement.writes
  var dirtyComposites = {}
  Object.keys(writes).forEach(function (id) {
    writes[id].forEach(function (write) {
      // Root mounting (innerHTML set) is recorded with an ID of ''
      if (id !== '') {
        measurement.hierarchy[id].forEach(function (c) {
          return (dirtyComposites[c] = true)
        })
      }
    })
  })
  var allIDs = _assign({}, measurement.exclusive, measurement.inclusive)

  for (var id in allIDs) {
    var isDirty = false
    // See if any of the DOM operations applied to this component's subtree.
    if (dirtyComposites[id]) {
      isDirty = true
    }
    // check if component newly created
    if (measurement.created[id]) {
      isDirty = true
    }
    if (!isDirty && measurement.counts[id] > 0) {
      cleanComponents[id] = true
    }
  }
  return cleanComponents
}

var ReactDefaultPerfAnalysis = {
  getExclusiveSummary: getExclusiveSummary,
  getInclusiveSummary: getInclusiveSummary,
  getDOMSummary: getDOMSummary,
  getTotalTime: getTotalTime
}

module.exports = ReactDefaultPerfAnalysis
